perm filename QPROC.SAI[1,JRA] blob sn#159073 filedate 1975-05-12 generic text, type T, neo UTF8
COMMENT ⊗   VALID 00016 PAGES 
RECORD PAGE   DESCRIPTION
 00001 00001
 00003 00002	BEGIN "QPROC"
 00005 00003	PROCEDURE DO_INPUT(REFERENCE STRING SOURCEFILES
 00009 00004	SIMPLE PROCEDURE INPUTERR(REFERENCE STRING CARD, CARD_AS_READ
 00011 00005	    SIMPLE PROCEDURE DO_PARMS
 00016 00006	    SIMPLE PROCEDURE CHECKPARMS
 00018 00007	    SIMPLE PROCEDURE DO_SCORES(INTEGER NQ)
 00025 00008		α More of DO_SCORES -- CARD has been completely scanned
 00027 00009	    α The main part of PROCEDURE DO_INPUT
 00029 00010		α More of main part of DO_INPUT -- input file now ready to be read
 00033 00011	PROCEDURE DO_OUTPUT(REFERENCE STRING RESULTFILE
 00037 00012	    SIMPLE PROCEDURE HEADING1(SAFE STRING ARRAY PARMS
 00040 00013	    α Main part of procedure DO_OUTPUT
 00043 00014		α Print scores for instructors
 00047 00015		α Print data for t.a.s
 00051 00016	α The main part of the main program
 00052 ENDMK
⊗;
BEGIN "QPROC"

DEFINE !N_Q=14, !NINS_Q=13, !NTA_Q=!N_Q-!NINS_Q,
       !CS=1, !IN=2, !TA=3, !EN=4,
       !MAXSCORE=11,
       !NPARMS=4,
       NO="NOT", STARTLOOP="WHILE TRUE DO ", α="COMMENT",
       TIL="STEP 1 UNTIL",
       PAST_BLANKS=1, UP_TO_COMMA=2, UP_TO_SEMICOLON=3, UP_TO_EQ=4,
       UP_TO_BLANK=5, UP_TO_CRLF=6,
       !SYSPRG="NULL",
       CR='15, LF='12, CRLF="('15&'12)", FF='14, TAB='11;

INTEGER Q;
SAFE INTEGER ARRAY TOTALANS[1:!N_Q], 	α for finding overall mean;
		   TOTALSCORES[1:!N_Q];
SAFE REAL ARRAY OVMEANS[1:!N_Q];

PRELOAD_WITH [14] 11;
SAFE INTEGER ARRAY MAXSCORES[1:!N_Q];

STRING SOURCEFILES, 	α file names -- this for input;
       RESULTFILE;	α and this for output;

REQUIRE 4000 NEW_ITEMS;
STRING ITEM CS_NAME, INSTRUCTOR, TEACHASST, ENROLLMENT, NUMBER_RESPS;
ITEM ISCORES, TSCORES;
INTEGER ITEM NUMBER_OF_INS, NUMBER_OF_TAS;
LIST CLASSLIST;
LABEL LBL;
PROCEDURE DO_INPUT(REFERENCE STRING SOURCEFILES;
		   REFERENCE LIST CLASSLIST;
		   SAFE INTEGER ARRAY MAXSCORES, TOTALANS, TOTALSCORES);
BEGIN "DO_INPUT"
    BOOLEAN FOLLOWSPARM, ERROR;
    STRING CARD, CARD_AS_READ, 	α input images;
	   FILENAME;
    INTEGER I,
            CARDNDX,  	α index of current card image in file;
	    CARDTYPE,	α i.e., score or parm;
	    NRESPS,	α # responding in the class;
	    INCHAN, BRCHAR, EOF,  α for i/o;
	    NQ,  α # score sets on a card (to avoid typing if no t.a.);
	    NINS, NTAS;

    SAFE STRING ITEMVAR ARRAY PARMS[1:!NPARMS];  α for picking off parms and 
						   printing out error messages;
    SAFE STRING ARRAY PARMVALUES[1:!NPARMS];

    STRING ITEMVAR CLASS;

    LIST ISCLIST, TSCLIST;  α to contain array items;

    SAFE INTEGER ARRAY ISCINIT[1:!NINS_Q,0:!MAXSCORE],	α for scores;
		       TSCINIT[1:!NTA_Q,0:!MAXSCORE];

SIMPLE PROCEDURE INPUTERR(REFERENCE STRING CARD, CARD_AS_READ;
			  INTEGER CARDNDX;
			  STRING FILENAME, ERRMSG);
BEGIN "INPUTERR"
    CLRBUF;
    OUTSTR(CRLF & "*** FILE=" & FILENAME & "   CARD=" & CVS(CARDNDX) & CRLF);
    OUTSTR(CARD_AS_READ[1 TO LENGTH(CARD_AS_READ)-LENGTH(CARD)] & LF & CARD);
    OUTSTR(CRLF & "*** " & ERRMSG);
    OUTSTR(CRLF & "RETYPE ENTIRE CARD." & CRLF & "→");
    LODED(CARD_AS_READ);
    CARD_AS_READ ← CARD ← INCHWL;
END "INPUTERR";





SIMPLE INTEGER PROCEDURE CLASSIFY(STRING C);
α Returns 2 if character is alphabetic, 3 if numeric, and 0 if neither;
IF C≥"0" AND C≤"9" THEN RETURN(3)
ELSE IF C≥"A" AND C≤"Z" THEN RETURN(2)
ELSE RETURN(0);
    SIMPLE PROCEDURE DO_PARMS;
    α CARD contains parameters, which are scanned off and entered into
      the associative store.  An error found anywhere on the card causes
      rejection of the entire card;
 
    BEGIN "DO_PARMS"
	SAFE BOOLEAN OWN ARRAY PARMSEEN[1:!NPARMS]; 
	STRING PARMNAME, PARMFIELD, NAME;
	STRING ITEMVAR INS, TA;
	INTEGER PARMNUM, I;
	INTEGER ARRAY ITEMVAR ISC, TSC;

	FOR I←1 TIL !NPARMS DO PARMSEEN[I]←FALSE;

	WHILE CARD DO
	α Scan off parameter and do the appropriate thing;
	BEGIN
	    PARMFIELD←SCAN(CARD, UP_TO_SEMICOLON, BRCHAR);
	    PARMNAME←SCAN(PARMFIELD, UP_TO_EQ, BRCHAR);

	    PARMNUM←0;
	    FOR I←1 TIL !NPARMS DO
		IF PARMNAME=DATUM(PARMS[I]) THEN  α note use of numeric compare;
		BEGIN
		    PARMNUM←I;  
		    PARMSEEN[I]←TRUE;
		    PARMVALUES[I]←PARMFIELD;
		    DONE;
		END;

	    IF NO PARMNUM THEN
	    BEGIN
		INPUTERR(CARD, CARD_AS_READ, CARDNDX, 
			 FILENAME, "INVALID PARAMETER.");
		α CARD and CARD_AS_READ now have new values;
		α Undo all parms specified on old card;
		FOR I←1 TIL !NPARMS DO
		    IF PARMSEEN[I] THEN 
		    BEGIN
			PARMSEEN[I]←FALSE;
			PARMVALUES[I]←NULL;
		    END;
	    END;

	    α Left-justify CARD again;
	    SCAN(CARD, PAST_BLANKS, BRCHAR);
	END;


	IF NO PARMNUM THEN RETURN;  α since last parm processed was bad,
				      was uncorrected, and therefore
				      invalidated the whole card;
	α Otherwise add parms to the associative store;
	FOR I←1 TIL !NPARMS DO IF PARMSEEN[I] THEN CASE I OF
	BEGIN

[!CS]	    BEGIN
		DATUM(CLASS)←PARMVALUES[!CS];
		MAKE CS_NAME ⊗ CLASS ≡ NEW(PARMVALUES[!CS]);
	    END;

[!IN]	    BEGIN
		PARMFIELD←PARMVALUES[!IN];
		NINS←0;
		SCAN(PARMFIELD, PAST_BLANKS, BRCHAR);
		IF BRCHAR THEN WHILE PARMFIELD DO
		α Pick instructor names out of list;
		BEGIN
		    NAME←SCAN(PARMFIELD, UP_TO_COMMA, BRCHAR);
		    SCAN(PARMFIELD, PAST_BLANKS, BRCHAR);
		    INS←NEW(NAME);
		    MAKE INSTRUCTOR ⊗ CLASS ≡ INS;
		    ISC←NEW(ISCINIT);  α array for scores for the instr;
		    MAKE ISCORES ⊗ INS ≡ ISC;
		    PUT ISC IN ISCLIST AFTER ∞;
		    NINS←NINS+1;
		END;
		MAKE NUMBER_OF_INS ⊗ CLASS ≡ NEW(NINS);
	    END;

[!TA]	    BEGIN
		PARMFIELD←PARMVALUES[!TA];
		NTAS←0;
		SCAN(PARMFIELD, PAST_BLANKS, BRCHAR);
		IF BRCHAR THEN WHILE PARMFIELD DO
		α Pick t.a. names from list and enter them;
		BEGIN
		    NAME←SCAN(PARMFIELD, UP_TO_COMMA, BRCHAR);
		    SCAN(PARMFIELD, PAST_BLANKS, BRCHAR);
		    TA←NEW(NAME);
		    MAKE TEACHASST ⊗ CLASS ≡ TA;
		    TSC←NEW(TSCINIT);  α array for scores for the t.a.;
		    MAKE TSCORES ⊗ TA ≡ TSC;
		    PUT TSC IN TSCLIST AFTER ∞;
		    NTAS←NTAS+1;
		END;
		MAKE NUMBER_OF_TAS ⊗ CLASS ≡ NEW(NTAS);
	    END;

[!EN]	    MAKE ENROLLMENT ⊗ CLASS ≡ NEW(PARMVALUES[!EN])

	END;
    END "DO_PARMS";
    SIMPLE PROCEDURE CHECKPARMS;
    α Checks to see that all parms have been specified before score
      processing begins;
    BEGIN "CHECKPARMS"
	BOOLEAN ALLPARMS;
	STRING ITEMVAR P;
	INTEGER I;
	ALLPARMS←TRUE;
	FOR I←1 TIL !NPARMS DO
	    ALLPARMS←ALLPARMS AND PARMVALUES[I];
	α Should have specified a blank field for an empty parm;
	IF NOT ALLPARMS THEN
	BEGIN
	    STRING TEMP;
	    CLRBUF;
	    FOR I←1 TIL !NPARMS DO
	    BEGIN
		OUTSTR(CRLF & DATUM(PARMS[I]) & "=");
		IF PARMS[I] ⊗ CLASS ≡ ANY THEN
		    FOREACH P | PARMS[I] ⊗ CLASS ≡ P DO
			OUTSTR("""" & DATUM(P) & """  ")
		ELSE OUTSTR("*** MISSING ***");
	    END;
	    OUTSTR(CRLF & LF & "TYPE IN PARMS CARD WITH REMAINING PARMS."
		   & CRLF & "→");
	    TEMP←CARD_AS_READ;
	    CARD_AS_READ←CARD←INCHWL;
	    DO_PARMS;
	    CARD_AS_READ←CARD←TEMP;
	END;

    END "CHECKPARMS";
    SIMPLE PROCEDURE DO_SCORES(INTEGER NQ);
    α CARD contains exactly NQ sets of scores.
      A set of scores is either a single score, or more than one score, each
      separated by commas.  Depending on the question being processed, there
      must be exactly one score in a set corresponding to each instructor or
      t.a. in the class.  A single score is "distributed" over each instructor
      or t.a.  A set of scores must contain no blanks.
      Possible errors:  too many or not enough sets of scores,
			too many or not enough scores in a set,
			invalid score (too big or too little).
      In case of error, the entire card is rejected.
      At least one instructor per class is assumed.
      After the scores are successfully read, they are entered into the 
      associative store;

    BEGIN "DO_SCORES"
	INTEGER Q, Q1, NSC, I, J;
        SHORT INTEGER K;  SHORT REAL SC;
	INTEGER ARRAY ITEMVAR ISC, TSC;
	STRING SCODE, SCOREFIELD;

	SCODE←NULL;
	Q←1;  α question number being processed;
	α Here is where it is assumed that there's ≥ one instructor;
	WHILE CARD OR Q≤NQ DO
	α Pick each score off card and put code for it into SCODE;
	BEGIN "SCAN_CARD"

	    IF Q>NQ THEN
	    BEGIN
		INPUTERR(CARD, CARD_AS_READ, CARDNDX, FILENAME,
			 "TOO MANY SETS OF SCORES.");
		α CARD and CARD_AS_READ are now replaced;
		SCODE←NULL;  α Discard code accumulated so far;
		Q←1;  α Restart question scanning;                    
		CONTINUE;
	    END
	    ELSE IF NO CARD THEN
	    BEGIN
		INPUTERR(CARD, CARD_AS_READ, CARDNDX, FILENAME,
			 "NOT ENOUGH SETS OF SCORES.");
		α CARD and CARD_AS_READ are now replaced;
		SCODE←NULL;  α Discard code accumulated so far;
		Q←1;  α Restart question scanning;                    
		CONTINUE;
	    END
	    ELSE NSC ← IF Q≤!NINS_Q THEN NINS ELSE NTAS;
	    α That defined the number of scores to be expected;

	    SCOREFIELD←SCAN(CARD, UP_TO_BLANK, BRCHAR);
	    SC←REALSCAN(SCOREFIELD, BRCHAR);

	    α Two possibilities:  in SCOREFIELD is either 
              a single number, which is the score for all instrs or t.a.s, or
	      a list of numbers separated by commas -- exactly one score for
		each instr or t.a. (depending on the question);

	    IF NO BRCHAR THEN  α just one score in SCOREFIELD;
		IF (K←SC←2.0*SC)≥0 AND K≤MAXSCORES[Q] THEN
		    FOR I←1 TIL NSC DO SCODE←SCODE & K
		ELSE  α invalid score;
		BEGIN
		    INPUTERR(CARD, CARD_AS_READ, CARDNDX, 
			     FILENAME, "INVALID SCORE.");
		    α CARD and CARD_AS_READ are now replaced;
		    SCODE←NULL;  α Discard code accumulated so far;
        	    Q←0;  α Restart question scanning;                    
		END

	    ELSE  α SCOREFIELD contains ≥1 score -- must have correct number;
	    FOR I←1 TIL NSC DO
	    BEGIN "SCAN_SET"
		IF (K←SC←2.0*SC)≥0 AND K≤MAXSCORES[Q] THEN
		    SCODE←SCODE & K  α Encode Ith score;
		ELSE  α invalid score;
		BEGIN
		    INPUTERR(CARD←SCOREFIELD & CARD, CARD_AS_READ,
			     CARDNDX, FILENAME, "INVALID SCORE.");
		    α CARD and CARD_AS_READ are now replaced;
		    SCOREFIELD←SCODE←NULL;  α Discard code accumulated so far;
		    Q←0;  α Restart question scanning;                    
		    DONE;
		END;
		IF BRCHAR>0  α from the scan for the Ith score; THEN
		    IF I≠NSC THEN  α get the I+1st score;
			SC←REALSCAN(SCOREFIELD, BRCHAR)
		    ELSE  α there was a break after getting the NSCth score;
		    BEGIN
			INPUTERR(CARD, CARD_AS_READ, CARDNDX, 
				 FILENAME, "TOO MANY SCORES IN SET.");
			α CARD and CARD_AS_READ are now replaced;
			SCODE←NULL;  α Discard code accumulated so far;
			Q←0;  α Restart question scanning;                    
			DONE;
		    END
		ELSE  α no more scores;  IF I≠NSC THEN
		BEGIN
		α Not enough scores for all instrs or t.a.s;
		    INPUTERR(CARD, CARD_AS_READ, CARDNDX,
			     FILENAME, "NOT ENOUGH SCORES IN SET.");
		    α CARD and CARD_AS_READ get new values;
		    SCODE←NULL;  α Code from old card now worthless;
		    Q←0;  α Restart question scan;
		    DONE;
		END 
	    END "SCAN_SET";

	    SCAN(CARD, PAST_BLANKS, BRCHAR);
	    Q←Q+1;
	END "SCAN_CARD";
	α More of DO_SCORES -- CARD has been completely scanned;

	IF NOT EQU(SCODE, NULL) THEN
	BEGIN
	    NRESPS←NRESPS+1;

	    FOR Q←1 TIL !NINS_Q DO FOR I←1 TIL NINS DO
	    BEGIN
		ISC←ISCLIST[I];
		IF J←LOP(SCODE) THEN
		α a response was to question q for instr i;
		BEGIN
		    TOTALANS[Q]←TOTALANS[Q]+1;
		    TOTALSCORES[Q]←TOTALSCORES[Q]+J;  α no need to add if 0;
		END;
		DATUM(ISC)[Q,J]←DATUM(ISC)[Q,J]+1;  α Record score;
	    END;

	    FOR Q←1 TIL !NTA_Q DO FOR I←1 TIL NTAS DO
	    BEGIN
		TSC←TSCLIST[I];
		IF J←LOP(SCODE) THEN
		α a response was to question q for t.a. i;
		BEGIN
		    Q1←Q+!NINS_Q;
		    TOTALANS[Q1]←TOTALANS[Q1]+1;
		    TOTALSCORES[Q1]←TOTALSCORES[Q1]+J;  α no need to add if 0;
		END;
		DATUM(TSC)[Q,J]←DATUM(TSC)[Q,J]+1;  α Record score;
	    END;

	END;

    END "DO_SCORES";
    α The main part of PROCEDURE DO_INPUT;

    OPEN(INCHAN←GETCHAN, "DSK", 1, 2, 0, 150, BRCHAR, EOF);

    SETBREAK(PAST_BLANKS, " 	", NULL, "XNR");
    SETBREAK(UP_TO_COMMA, ",", NULL, "INS");
    SETBREAK(UP_TO_SEMICOLON, ";", NULL, "INS");
    SETBREAK(UP_TO_EQ, "=", NULL, "INS");
    SETBREAK(UP_TO_BLANK, " 	", NULL, "INS");
    SETBREAK(UP_TO_CRLF, LF, CR, "INS");

    DATUM(CS_NAME)←"CS";
    DATUM(INSTRUCTOR)←"IN";
    DATUM(TEACHASST)←"TA";
    DATUM(ENROLLMENT)←"EN";
    PARMS[!CS]←CS_NAME;
    PARMS[!IN]←INSTRUCTOR;
    PARMS[!TA]←TEACHASST;
    PARMS[!EN]←ENROLLMENT;

    FOLLOWSPARM←TRUE;
    CLASS←NEW(NULL);

    α Now process input;
    WHILE SOURCEFILES DO
    BEGIN "DO_EACH_FILE"
	FILENAME←SCAN(SOURCEFILES, UP_TO_COMMA, BRCHAR);
	STARTLOOP  α to look up the input file;
	BEGIN
	    LOOKUP(INCHAN, FILENAME, ERROR);
	    IF NO ERROR THEN DONE;
	    OUTSTR(CRLF & "INVALID SOURCE INPUT FILE:  ");
	    OUTSTR(FILENAME);
	    OUTSTR(CRLF & "RESPECIFY?  ");
	    IF NO FILENAME←INCHWL THEN
		IF NO FILENAME←SCAN(SOURCEFILES, UP_TO_COMMA, BRCHAR) THEN DONE;
	END;
	IF NO FILENAME THEN CONTINUE;
	α More of main part of DO_INPUT -- input file now ready to be read;

	CARD←INPUT(INCHAN, UP_TO_CRLF);
	IF EQU(CARD[1 FOR 9],"COMMENT ⊗") THEN	α flush directory page;
	BEGIN
	    OUTSTR("Skipping directory page." & CRLF);
	    WHILE (CARD←INPUT(INCHAN, UP_TO_CRLF))≠FF DO;
	    CARD←CARD[2 TO ∞];
	END;
	CARD_AS_READ←CARD;
	CARDNDX←1;
	WHILE NOT EOF DO
	BEGIN "DO_EACH_CARD"   α process the CARDNDX'th card in the file;
	    IF INCHRS≠-1 THEN
		OUTSTR(CRLF & "File = " & FILENAME & "   Card = " & CVS(CARDNDX));

	    SCAN(CARD, PAST_BLANKS, BRCHAR);
	    CARDTYPE←CLASSIFY(BRCHAR);
	    CASE CARDTYPE OF
	    BEGIN

    α error;    BEGIN 
		    INPUTERR(CARD, CARD_AS_READ, CARDNDX, 
			     FILENAME, "UNRECOGNIZABLE CARD.");
		    α CARD and CARD_AS_READ are replaced by corrected versions;
		    IF CARD THEN CONTINUE;  α skips past read;
		END;

    α comment;  BEGIN  α card is to be ignored; END;

    α parm;     BEGIN 
		    IF NOT FOLLOWSPARM THEN
		    α End of a set of scores. 
		      Finish off last class and start a new one;
		    BEGIN
			MAKE NUMBER_RESPS ⊗ CLASS ≡ NEW(CVS(NRESPS));
			PUT CLASS IN CLASSLIST AFTER ∞;
			CLASS←NEW(NULL);
			FOR I←1 TIL !NPARMS DO PARMVALUES[I]←NULL;
			NINS←NTAS←0;
			ISCLIST←TSCLIST←NIL;
		    END;
		    DO_PARMS;
		    FOLLOWSPARM←TRUE;
		END;

    α score;    BEGIN
		    IF FOLLOWSPARM THEN 
		    α All parms must have been specified for score processing
		      to start.  CHECKPARMS checks that;
		    BEGIN
			CHECKPARMS;
			α Assuming here that there's at least one instr;
			NQ←IF NTAS THEN !N_Q ELSE !NINS_Q;
			NRESPS←0;  α since starting a new file;
		    END;
		    DO_SCORES(NQ);  α Argument is # of score sets on CARD;
		    FOLLOWSPARM←FALSE;
		END;
	    END;

	    IF (CARD←INPUT(INCHAN, UP_TO_CRLF))=FF THEN CARD←CARD[2 TO ∞];
	    CARD_AS_READ←CARD;
	    CARDNDX←CARDNDX+1;

	END "DO_EACH_CARD";
    END "DO_EACH_FILE";

    α Finish up last class;
    MAKE NUMBER_RESPS ⊗ CLASS ≡ NEW(CVS(NRESPS));
    PUT CLASS IN CLASSLIST AFTER ∞;

    CLOSIN(INCHAN);

END "DO_INPUT";
PROCEDURE DO_OUTPUT(REFERENCE STRING RESULTFILE;
		    REFERENCE LIST CLASSLIST;
		    SAFE INTEGER ARRAY MAXSCORES;
		    SAFE REAL ARRAY OVMEANS);
BEGIN "DO_OUTPUT"

    REQUIRE "⊂⊃⊂⊃" DELIMITERS;
    α The following macro clears the current MRPP3 mode, enters center mode,
      and sets the left and right margins for centering to L and R, resp.;
    DEFINE TMPMARG(L,R)=⊂ OUT(OUTCHAN, "~←" & L & "~→l~←" & R & "~→r~c") ⊃;

    PRELOAD_WITH 
	"General rating of the course as a whole." & CRLF,
	"Give the instructor an overall rating." & CRLF,
	"Instructor -- preparedness and organization of lectures" & CRLF,
	"Instructor -- clarity of explanations" & CRLF,
	"Instructor -- guidance of class discussion, question handling" & CRLF,
	"Instructor -- willingness to help students" & CRLF,
	"Instructor -- accessibility to students" & CRLF,
	"Instructor -- encouragement of your interest in the subject" & CRLF,
	"Rate the instructor's overall effort." & CRLF,
	"Course content -- pace at which material was covered" & CRLF,
	"Course content -- continuity" & CRLF,
	"Course content -- clarity of course goals" & CRLF,
	"Course content -- value of work expended" & CRLF;
    SAFE OWN STRING ARRAY QUESTION[1:!N_Q];
    PRELOAD_WITH [8] 1, 2, 3, 1, 1, 1;
    SAFE OWN INTEGER ARRAY ANSTYPE[1:!N_Q];
    PRELOAD_WITH 
	"excellent~.", "good~.", "satisfactory~.", "poor~.", "terrible~.",
	"extraordinary~.", "a great deal~.", "a fair amount~.", 
		"very little~.", "almost zero~.",
	"much too fast~.", "too fast~.", "about right~.", "too slow~.", 
		"much too slow~.";
    SAFE OWN STRING ARRAY ANSWER[1:3, 1:!MAXSCORE % 2];

    INTEGER OUTCHAN, M, R, Q, Q1, I, SUM, RESPS, ERROR, J, NINS, NTAS;
    INTEGER ITEMVAR X;
    SET PSET;
    STRING ITEMVAR INS, TA, CLASS, U;
    INTEGER ARRAY ITEMVAR ISC, TSC;

    PRELOAD_WITH [!NINS_Q % 2] 0, 1, [!N_Q - 1 - !NINS_Q % 2] 0;
    SAFE OWN BOOLEAN ARRAY SKIPAFTER[1:!N_Q];  α for inserting page breaks;

    SAFE STRING ARRAY PARMS[1:!NPARMS+1];  α for parm vals and also # resps;
    SIMPLE PROCEDURE HEADING1(SAFE STRING ARRAY PARMS;
			      INTEGER N;
			      STRING ITEMVAR P);
    α A form-feed is output.  A heading listing the parameters
      of the given class follows.  N is assumed to be the number of people
      involved (either instructors or t.a.s) and if N is more than 1, a
      subheading will be printed for person P;

    BEGIN "HEADING1"
	OUT(OUTCHAN, FF & "~F1~C");  α Select font;
	OUT(OUTCHAN, PARMS[1]);  α Class title;
	OUT(OUTCHAN, CRLF & "~F2Instructor:  ");  α New font;
	OUT(OUTCHAN, PARMS[2]);  α Output list of instructors;
	OUT(OUTCHAN, "~REnrolled:  ");
	OUT(OUTCHAN, PARMS[4]);  α Output enrollment (right-justified);
	OUT(OUTCHAN, CRLF & "Teaching asst:  ");
	OUT(OUTCHAN, PARMS[3]);  α Output list of t.a.s;
	OUT(OUTCHAN, "~RResponses:  ");
	OUT(OUTCHAN, PARMS[5]);  α Output # responses (right-justified);
	OUT(OUTCHAN, CRLF & LF & LF);
	IF N>1 THEN  α print subheading;
	BEGIN
	    OUT(OUTCHAN, "Ratings for " & DATUM(P));
	    OUT(OUTCHAN, CRLF & LF);
	END;
    END "HEADING1";



    SIMPLE PROCEDURE HEADING2(SAFE STRING ARRAY PARMS;
			      INTEGER N;
			      STRING ITEMVAR P);
    α Class heading is printed;
    BEGIN "HEADING2"
	OUT(OUTCHAN, FF & "~F1~C");  α Select font;
	OUT(OUTCHAN, PARMS[1]);  α Class title;
	OUT(OUTCHAN, CRLF & LF);
    END "HEADING2";



    SIMPLE PROCEDURE HEADING3(SAFE STRING ARRAY PARMS;
			      INTEGER N;
			      STRING ITEMVAR P;
			      BOOLEAN J);
    α T.a. heading = class heading if at top of page (2 t.a.s per page).
      If more than one t.a., subheading is printed;
    BEGIN "HEADING3"
	IF J THEN HEADING2(PARMS, N, P);
	IF N>1 THEN  α print subheading;
	BEGIN
	    OUT(OUTCHAN, "~F2Ratings for " & DATUM(P));
	    OUT(OUTCHAN, CRLF & LF);
	END;
    END "HEADING3";

    α Main part of procedure DO_OUTPUT;

    OPEN(OUTCHAN←GETCHAN, "DSK", 1, 0, 2, 0, I, J);
    STARTLOOP
    BEGIN
	ENTER(OUTCHAN, RESULTFILE, ERROR);
	IF NO ERROR THEN DONE;
	OUTSTR(CRLF & "INVALID RESULT FILE:  ");
	OUTSTR(RESULTFILE);
	OUTSTR(CRLF & "RESPECIFY?  ");
	RESULTFILE←INCHWL;
    END;

    OUT(OUTCHAN, "~");  α Output leading escape character for MRPP3;
    OUT(OUTCHAN, "~M1SET1" & !SYSPRG & ";~M2NGB25" & !SYSPRG
		 & ";~M3NGR25" & !SYSPRG & ";~M4NGR20" & !SYSPRG
		 & ";~." & CRLF);  α Load fonts;

    α Set tabs, each 1/15th farther across the page;
    OUT(OUTCHAN, "~←R~-L~/=15;~→A~←L~→S~." & CRLF);
    OUT(OUTCHAN, "~+A~→T~+A~→U~+A~→V~+A~→W~+A~→X~+A~→Y~+A~→Z~+A~→[~+A~→\~+A~→]" & CRLF);
    OUT(OUTCHAN, "~+A~→↑~+A~→←~+A~→`~+A~→a~+A~→b~+A~→c~." & CRLF);

    SETFORMAT(0,1);  α print to one decimal place;

    FOREACH CLASS | CLASS IN CLASSLIST DO
    α Print class totals;
    BEGIN "DO_EACH_CLASS"
	
	α Fill up PARMS array;
	FOR I←1 TIL !NPARMS DO PARMS[I]←NULL;
	PARMS[1]←DATUM(CLASS);  α Course name;
	IF INCHRS≠-1 THEN OUTSTR(CRLF & "Class = " & PARMS[1]);
	FOREACH U | ENROLLMENT ⊗ CLASS ≡ U DO PARMS[4]←DATUM(U);  α Enrollment;
	FOREACH U | NUMBER_RESPS ⊗ CLASS ≡ U DO PARMS[5]←DATUM(U);  α # resps;
	PSET←INSTRUCTOR ⊗ CLASS;  α Form string of instructors' names;
	U←LOP(PSET);  α Always at least one instructor;
	PARMS[2]←DATUM(U);
	FOREACH U | U IN PSET DO PARMS[2]←PARMS[2] & ", " & DATUM(U);
	IF (PSET←TEACHASST ⊗ CLASS)≠NIL THEN  α form string of t.a.s' names;
	BEGIN
	    U←LOP(PSET);
	    PARMS[3]←DATUM(U);
	    FOREACH U | U IN PSET DO PARMS[3]←PARMS[3] & ", " & DATUM(U);
	END;
	α Print scores for instructors;
	FOREACH X | NUMBER_OF_INS ⊗ CLASS ≡ X DO NINS←DATUM(X);
	FOREACH INS, ISC | INSTRUCTOR ⊗ CLASS ≡ INS 
			   AND ISCORES ⊗ INS ≡ ISC DO
	BEGIN "DO_INSTR"
	    HEADING1(PARMS, NINS, INS);  α Print heading;
	    FOR Q←1 TIL !NINS_Q DO  α print scores for each instr question;
	    BEGIN "DO_INSTR_Q"
		IF SKIPAFTER[Q] THEN  α need to start new page;
		    HEADING2(PARMS, NINS, INS);
		OUT(OUTCHAN, "~F3" & QUESTION[Q]);  α New font & print q;
		α Note that QUESTION elements have CRLF already attached;
		OUT(OUTCHAN, "~F4");  α Change font;

		α Print out answer headings;
		M←"S";
		FOR I←1 TIL MAXSCORES[Q] % 2 DO
		BEGIN
		    TMPMARG(⊂M⊃, ⊂(M+3)⊃);
		    OUT(OUTCHAN, ANSWER[ANSTYPE[Q], I]);
		    M←M+2;
		END;
		TMPMARG(⊂"↑"⊃, ⊂"a"⊃);
		OUT(OUTCHAN, "mean~.");
		TMPMARG(⊂"a"⊃, ⊂"b"⊃);
		OUT(OUTCHAN, "overall mean" & CRLF & LF);

		α Print out scores;
		M←"S";  SUM←RESPS←0;
		FOR R←1 TIL MAXSCORES[Q] DO
		BEGIN
		    TMPMARG(⊂M⊃, ⊂(M←M+1)⊃);  α Set margins and center;
		    IF I←DATUM(ISC)[Q,R] THEN
		    BEGIN
			OUT(OUTCHAN, CVS(I));
			SUM←SUM+R*I;  α weighted sum of scores being formed;
			RESPS←RESPS+I;  α # answers to question q;
		    END
		    ELSE OUT(OUTCHAN, "--");
		    OUT(OUTCHAN, "~.");
		END;
		α If any responses, print instructor mean for question Q;
		IF RESPS THEN 
		BEGIN
		    TMPMARG(⊂"↑"⊃, ⊂"a"⊃);
		    OUT(OUTCHAN, CVF(SUM/(RESPS*2)));
		    OUT(OUTCHAN, "~.");
		END;
		α Print overall mean;
		TMPMARG(⊂"a"⊃, ⊂"b"⊃);
		OUT(OUTCHAN, CVF(OVMEANS[Q]) & CRLF);

		α Print percentages;
		M←"S";
		FOR R←1 TIL MAXSCORES[Q] DO
		BEGIN
		    TMPMARG(⊂M⊃, ⊂(M←M+1)⊃);
		    OUT(OUTCHAN, IF I←DATUM(ISC)[Q,R] THEN 
				     CVF((100*I)/RESPS) & "%"
				 ELSE "--");
		    OUT(OUTCHAN, "~.");
		END;
		OUT(OUTCHAN, CRLF & LF & LF);
	    END "DO_INSTR_Q";

	END "DO_INSTR";

	α Print data for t.a.s;

	FOREACH X | NUMBER_OF_TAS ⊗ CLASS ≡ X DO NTAS←DATUM(X);
	J←FALSE;
	FOREACH TA, TSC | TEACHASST ⊗ CLASS ≡ TA 
			   AND TSCORES ⊗ TA ≡ TSC DO
	BEGIN "DO_TA"
	    HEADING3(PARMS, NTAS, TA, J←NOT J);  α Print heading (2 t.a.s per page);
	    FOR Q←1 TIL !NTA_Q DO  α print scores for each t.a. question;
	    BEGIN "DO_TA_Q"
		Q1←Q+!NINS_Q;  α Q1 is the actual question index;
		IF SKIPAFTER[Q1] THEN  α need to start new page;
		    HEADING2(PARMS, NTAS, TA);
		OUT(OUTCHAN, "~F3" & QUESTION[Q1]);  α New font & print q;
		α Note that QUESTION elements have CRLF already attached;
		OUT(OUTCHAN, "~F4");  α Change font;

		α Print out answer headings;
		M←"S";
		FOR I←1 TIL MAXSCORES[Q1] % 2 DO
		BEGIN
		    TMPMARG(⊂M⊃, ⊂(M+3)⊃);
		    OUT(OUTCHAN, ANSWER[ANSTYPE[Q1], I]);
		    M←M+2;
		END;
		TMPMARG(⊂"↑"⊃, ⊂"a"⊃);
		OUT(OUTCHAN, "mean~.");
		TMPMARG(⊂"a"⊃, ⊂"b"⊃);
		OUT(OUTCHAN, "overall mean" & CRLF & LF);

		α Print out scores;
		M←"S";  SUM←RESPS←0;
		FOR R←1 TIL MAXSCORES[Q1] DO
		BEGIN
		    TMPMARG(⊂M⊃, ⊂(M←M+1)⊃);  α Set margins and center;
		    IF I←DATUM(TSC)[Q,R] THEN
		    BEGIN
			OUT(OUTCHAN, CVS(I));
			SUM←SUM+R*I;  α weighted sum of scores being formed;
			RESPS←RESPS+I;  α # answers to question q;
		    END
		    ELSE OUT(OUTCHAN, "--");
		    OUT(OUTCHAN, "~.");
		END;
		α If any responses, print t.a. mean for question Q;
		IF RESPS THEN 
		BEGIN
		    TMPMARG(⊂"↑"⊃, ⊂"a"⊃);
		    OUT(OUTCHAN, CVF(SUM/(RESPS*2)));
		    OUT(OUTCHAN, "~.");
		END;
		α Print overall mean;
		TMPMARG(⊂"a"⊃, ⊂"b"⊃);
		OUT(OUTCHAN, CVF(OVMEANS[Q1]) & CRLF);

		α Print percentages;
		M←"S";
		FOR R←1 TIL MAXSCORES[Q1] DO
		BEGIN
		    TMPMARG(⊂M⊃, ⊂(M←M+1)⊃);
		    OUT(OUTCHAN, IF I←DATUM(TSC)[Q,R] THEN 
				     CVF((100*I)/RESPS) & "%"
				 ELSE "--");
		    OUT(OUTCHAN, "~.");
		END;
		OUT(OUTCHAN, CRLF & LF & LF);
	    END "DO_TA_Q";

	END "DO_TA";

    END "DO_EACH_CLASS";

    CLOSO(OUTCHAN);

END "DO_OUTPUT";
α The main part of the main program;

α Get input and output file names;
OUTSTR(CRLF & "SOURCE FILES?  ");
SOURCEFILES←INCHWL;
OUTSTR("FILE FOR RESULTS?  ");
RESULTFILE←INCHWL;

α Start processing;
DO_INPUT(SOURCEFILES, CLASSLIST, MAXSCORES, TOTALANS, TOTALSCORES);
OUTSTR(CRLF & "INPUT PROCESSING COMPLETED." & CRLF);
LBL: FOR Q←1 TIL !N_Q DO OVMEANS[Q]←TOTALSCORES[Q]/(TOTALANS[Q]*2);
DO_OUTPUT(RESULTFILE, CLASSLIST, MAXSCORES, OVMEANS);
OUTSTR(CRLF & "OUTPUT PROCESSING COMPLETED." & CRLF);

END "QPROC";